package piano;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.util.Vector;

import javax.sound.midi.Instrument;
import javax.sound.midi.MidiChannel;
import javax.sound.midi.MidiSystem;
import javax.sound.midi.Sequence;
import javax.sound.midi.Sequencer;
import javax.sound.midi.Soundbank;
import javax.sound.midi.Synthesizer;
import javax.swing.JPanel;

public class PianoSynth {
	
	final int ON = 0, OFF = 1;
	final int OCTAVES = 6;
	final Color jfcBlue = new Color(204, 204, 255);
	Sequencer sequencer;
	Sequence sequence;
	Synthesizer synthesizer;
	Instrument instruments[];
	ChannelData channels[];
	ChannelData cc;
	Vector keys = new Vector();
	Vector whiteKeys = new Vector();
	Piano piano;
	
	public PianoSynth() {
		piano = new Piano();
	}
	
	public void open() {
		try {
			if (synthesizer == null) {
				if ((synthesizer = MidiSystem.getSynthesizer()) == null) {
					System.out.println("getSynthesizer() failed!");
					return;
				}
			}
			synthesizer.open();
			sequencer = MidiSystem.getSequencer();
			sequence = new Sequence(Sequence.PPQ, 10);
		} catch (Exception ex) { ex.printStackTrace(); return; }
		
		Soundbank sb = synthesizer.getDefaultSoundbank();
		if (sb != null) {
			instruments = synthesizer.getDefaultSoundbank().getInstruments();
			synthesizer.loadInstrument(instruments[0]);
		}
		MidiChannel midiChannels[] = synthesizer.getChannels();
		channels = new ChannelData[midiChannels.length];
		for (int i = 0; i < channels.length; i++) {
			channels[i] = new ChannelData(midiChannels[i], i);
		}
		cc = channels[0];
	}
	
	public void close() {
		if (synthesizer != null) {
			synthesizer.close();
		}
		if (sequencer != null) {
			sequencer.close();
		}
		sequencer = null;
		synthesizer = null;
		instruments = null;
		channels = null;
	}
	
	class ChannelData {
		MidiChannel channel;
		int velocity, pressure, bend, reverb;
		int row, col, num;
		
		public ChannelData(MidiChannel channel, int num) {
			this.channel = channel;
			this.num = num;
			velocity = pressure = bend = reverb = 64;
		}
	}
	
	public void playNote(int kNum){
		cc.channel.noteOn(kNum, cc.velocity);
	}
	
	public void stopNnote(int note) {
		cc.channel.noteOff(note, cc.velocity);
	}
	
	class Key extends Rectangle {
		int noteState = OFF;
		int kNum;
		public Key(int x, int y, int width, int height, int num) {
			super(x, y, width, height);
			kNum = num;
		}
		public boolean isNoteOn() {
			return noteState == ON;
		}
		public void on() {
			setNoteState(ON);
			cc.channel.noteOn(kNum, cc.velocity);
		}
		public void off() {
			setNoteState(OFF);
			cc.channel.noteOff(kNum, cc.velocity);
		}
		public void setNoteState(int state) {
			noteState = state;
		}
	}
	
	class Piano extends JPanel implements MouseListener {
		
		Vector blackKeys = new Vector();
		Key prevKey;
		final int keyWidth = 16, keyHeight = 80;
		
		public Piano() {
			setLayout(new BorderLayout());
			setPreferredSize(new Dimension(42 * keyWidth, keyHeight + 1));
			int transpose = 24;
			int whiteIDs[] = {0, 2, 4, 5, 7, 9, 11};
			
			for (int i = 0, x = 0; i < OCTAVES; i++) {
				for (int j = 0; j < 7; j++, x += keyWidth) {
					int keyNum = i * 12 + whiteIDs[j] + transpose;
					whiteKeys.add(new Key(x, 0, keyWidth, keyHeight, keyNum));
				}
			}
			for (int i = 0, x = 0; i < OCTAVES; i++, x += keyWidth) {
				int keyNum = i * 12 + transpose;
				blackKeys.add(new Key((x += keyWidth) - 4, 0, keyWidth/2, keyHeight/2, keyNum + 1));
				blackKeys.add(new Key((x += keyWidth) - 4, 0, keyWidth/2, keyHeight/2, keyNum + 3));
				x += keyWidth;
				blackKeys.add(new Key((x += keyWidth) - 4, 0, keyWidth/2, keyHeight/2, keyNum + 6));
				blackKeys.add(new Key((x += keyWidth) - 4, 0, keyWidth/2, keyHeight/2, keyNum + 8));
				blackKeys.add(new Key((x += keyWidth) - 4, 0, keyWidth/2, keyHeight/2, keyNum + 10));
			}
			keys.addAll(blackKeys);
			keys.addAll(whiteKeys);
			
			addMouseListener(this);
		}

		public void mousePressed(MouseEvent e) {
			prevKey = getKey(e.getPoint());
			if (prevKey != null) {
				prevKey.on();
				getParent().repaint();
			}
		}
		public void mouseReleased(MouseEvent e) {
			if (prevKey != null) {
				prevKey.off();
				getParent().repaint();
			}
		}
		public void mouseExited(MouseEvent e) {
			if (prevKey != null) {
				prevKey.off();
				getParent().repaint();
				prevKey = null;
			}
		}
		public void mouseClicked(MouseEvent e) { }
		public void mouseEntered(MouseEvent e) { }
		
		public Key getKey(Point point) {
			for (int i = 0; i < keys.size(); i++) {
				if (((Key) keys.get(i)).contains(point)) {
					return (Key) keys.get(i);
				}
			}
			return null;
		}
		
		public void paint(Graphics g) {
		    Graphics2D g2 = (Graphics2D) g;
		    Dimension d = getSize();

		    g2.setColor(Color.white);
		    g2.fillRect(0, 0, 42*keyWidth, keyHeight);

		    for (int i = 0; i < whiteKeys.size(); i++) {
		        Key key = (Key) whiteKeys.get(i);
		        if (key.isNoteOn()) {
		            g2.setColor(jfcBlue);
		            g2.fill(key);
		        }
		        g2.setColor(Color.black);
		        g2.draw(key);
		    }
		    for (int i = 0; i < blackKeys.size(); i++) {
		        Key key = (Key) blackKeys.get(i);
		        if (key.isNoteOn()) {
		            g2.setColor(jfcBlue);
		            g2.fill(key);
		            g2.setColor(Color.black);
		            g2.draw(key);
		        } else {
		            g2.setColor(Color.black);
		            g2.fill(key);
		        }
		    }
		}
		
	}



}
